/*
 * This project provides a number of implementations for indexing and evaluating
 * <i>Continuous Top-k Queries over Text Streams</i>
 * and has been developped as part of the work described in the article:
 * 
 * Vouzoukidou, N., Amann, B., & Christophides, V. (2012, October). 
 * Processing continuous text queries featuring non-homogeneous scoring functions.
 * In Proceedings of the 21st ACM international conference on Information and 
 * knowledge management (pp. 1065-1074). ACM.
 * http://dl.acm.org/citation.cfm?id=2398404
 * 
 * This software is distributed under the GNU General Public License and as an
 * additional condition of usage, please cite the aforementioned article.
 * 
 * Copyright (C) 2012 ICS/FORTH, Greece and 
 *               2012 LIP6, France
 * 
 * Author: Vouzoukidou Nelly, email: nelly.vouzoukidou (at) gmail (dot) com
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package gr.forth.ics.continuoustopk;

import gr.forth.ics.continuoustopk.metrics.TermStatistics;


/**
 * A Query represented as a set of terms.
 * The top-k list is also maintained without holding however, information about
 * the the actual items that the query has received. Only the top-k lists of scores
 * is maintained.
 * 
 * There is no public constructor provided. Instead three general static methods
 * are provided which create Queries with different characteristics.
 * 
 * @see newTopOne(String[], double[], double)
 * @see newSortedTopK(String[], double[], int)
 * @see newUnsortedTopK(String[], double[], int)
 *
 * @author Vouzoukidou Nelly, email: nelly.vouzoukidou (at) gmail (dot) com
 */
public abstract class Query extends TermVector implements TermStatistics, Cloneable {
    protected Query(Term[] terms, double[] weights) {
        super(terms, weights);
    }

    protected Query(String[] terms, double[] weights) {
        super(terms, weights);
    }

    protected Query(String[] terms) {
        super(terms, equalWeights(terms.length));
    }

    /**
     * Creates a new Query object containing only the top-1 item.
     *
     * <p>All terms must be distinct: no additional checks are perfomed to assure this.</p>
     * 
     * @param terms the set of terms contained in this query
     * @param weights the weights of the terms. The size of this array must be equal to the one of terms
     * @param minScore the minimum score of the query
     * 
     * @return a new Query object
     */
    public static Query newTopOne(String[] terms, double[] weights, double minScore) {
        return new TopOneQuery(terms, weights, minScore);
    }

    /**
     * Creates a new Query object maintaining a sorted list of the top-k of items.
     * 
     * <p>Since the top-k list is expected to be small, the list is a simple array holding
     * the values of scores. Thus, update complexity is linear to the number of elements
     * O(k), while {@link #minScore()} is performed in constant time.
     * </p>
     *
     * <p>All terms must be distinct: no additional checks are perfomed to assure this.</p>
     * 
     * @param terms the set of terms contained in this query
     * @param weights the weights of the terms. The size of this array must be equal to the one of terms
     * @param k the size of the top-k list
     * 
     * @return a new Query object
     */
    public static Query newSortedTopK(String[] terms, double[] weights, int k) {
        return new SortedTopKQuery(terms, weights, k);
    }

    /**
     * Creates a new Query object maintaining an unsorted list (stored as a static size array)
     * of the top-k of items.
     * 
     * <p>All terms must be distinct: no additional checks are perfomed to assure this.</p>
     * 
     * @param terms the set of terms contained in this query
     * @param weights the weights of the terms. The size of this array must be equal to the one of terms
     * @param k the size of the top-k list
     * 
     * @return a new Query object
     */
    public static Query newUnsortedTopK(String[] terms, double[] weights, int k) {
        return new UnsortedTopKQuery(terms, weights, k);
    }

    // <editor-fold defaultstate="collapsed" desc="convenience static constructors">
    public static Query newTopOne(String[] terms, double minScore) {
        return new TopOneQuery(terms, minScore);
    }

    public static Query newTopOne(String[] terms) {
        return new TopOneQuery(terms, 0);
    }

    public static Query newSortedTopK(String[] terms, int k) {
        return new SortedTopKQuery(terms, k);
    }

    public static Query newUnsortedTopK(String[] terms, int k) {
        return new UnsortedTopKQuery(terms, k);
    }
    // </editor-fold>

    private static double[] equalWeights(int size) {
        double[] weights = new double[size];
        double weight = 1.0 / size;
        for (int i = 0; i < weights.length; i++) {
            weights[i] = weight;
        }
        return weights;
    }

    /**
     * Returns the minimum query-item score in the top-k list of this query
     * @return the minimum score in for this query
     */
    public abstract double minScore();

    /**
     * Updates the top-k list of this query by adding this item.
     * The given score is the score assigned to the query-item pair and it 
     * must be greater than the minimum score of this query. <b> The query-item
     * score is assumed to be correct value as returned from the scoring fucntion
     * and no additional computation is perfomed</b>
     * 
     * @param item the item that is inserted in the top-k list
     * @param score the score assigned to this query-item pair
     * 
     * @throws IllegalArgumentException if {@code score} &lt minScore()
     * @see minScore()
     */
    public abstract void update(Item item, double score);

    @Override
    public abstract Query clone();

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj.getClass() != this.getClass()) {
            return false;
        }
        return super.equals(obj); // && minScore() == q.minScore();
    }

    public abstract void setMinScore(double minScore);

    @Override
    public String toString() {
        return super.toString() + " (" + minScore() + ")";
    }

    private int hashCode = 0;

    @Override
    public int hashCode() {
        if (hashCode == 0) {
            hashCode = super.hashCode();
//            hashCode = hashCode + (int) (minScore() * 1000);
        }
        return hashCode;
    }
}


// <editor-fold defaultstate="collapsed" desc="SortedTopKQuery">
class SortedTopKQuery extends Query {
    protected double[] topKList;

    SortedTopKQuery(Term[] terms, double[] weights, double[] topKList) {
        super(terms, weights);
        topKList = new double[topKList.length];
        for (int i = 0; i < topKList.length; i++) {
            this.topKList[i] = topKList[i];
        }
    }

    SortedTopKQuery(String[] terms, double[] weight, int k) {
        super(terms, weight);
        initTopKList(k);
    }

    SortedTopKQuery(String[] terms, int k) {
        super(terms);
        if (k < 1) {
            throw new IllegalArgumentException("k cannot be less than 1. Found " + k);
        }
        initTopKList(k);
    }

    @Override
    public Query clone() {
        return new SortedTopKQuery(terms, weights, topKList);
    }

    private void initTopKList(int k) {
        topKList = new double[k];
    }

    public double minScore() {
        // the array is sorted: the last element is always the correct one
        return topKList[topKList.length - 1];
    }

    public void update(Item item, double score) {
        int i;
        for (i = topKList.length - 2; i >= 0; i--) {
            if (score <= topKList[i]) {
                break;
            }
            topKList[i + 1] = topKList[i];
        }
        topKList[i + 1] = score;
    }

    @Override
    public void setMinScore(double minScore) {
        topKList[topKList.length - 1] = minScore;
    }
}
// </editor-fold>

// <editor-fold defaultstate="collapsed" desc="UnortedTopKQuery">
class UnsortedTopKQuery extends SortedTopKQuery {
    private int minScoreIndex;

    UnsortedTopKQuery(Term[] terms, double[] weights, double[] topKList, int minScoreIndex) {
        super(terms, weights, topKList);
        this.minScoreIndex = minScoreIndex;
    }

    UnsortedTopKQuery(String[] terms, double[] weight, int k) {
        super(terms, weight, k);
    }

    UnsortedTopKQuery(String[] terms, int k) {
        super(terms, k);
    }

    public Query clone() {
        return new UnsortedTopKQuery(terms, weights, topKList, minScoreIndex);
    }

    @Override
    public double minScore() {
        return topKList[minScoreIndex];
    }

    @Override
    public void update(Item item, double score) {
        topKList[minScoreIndex] = score;

        // find the new minimum (maybe its different than the one just inserted)
        double minScore = score;
        for (int i = 0; i < topKList.length; i++) {
            if (topKList[i] < minScore) {
                minScore = topKList[i];
                minScoreIndex = i;
            }
        }
    }

    @Override
    public void setMinScore(double minScore) {
        topKList[minScoreIndex] = minScore;
    }
}
// </editor-fold>

// <editor-fold defaultstate="collapsed" desc="TopOneQuery">
/**
 * A query that only holds a top-1 list of items.
 * Old elements that where in the top-1 list do not need to be stored, thus only
 * the minimum accepted score is maintained.
 */
class TopOneQuery extends Query {
    private double minScore = 0.0;

    TopOneQuery(Term[] terms, double[] weights, double minScore) {
        super(terms, weights);
        this.minScore = minScore;
    }

    TopOneQuery(String[] terms, double[] weights, double minScore) {
        super(terms, weights);
        this.minScore = minScore;
    }

    TopOneQuery(String[] terms, double minScore) {
        super(terms);
        this.minScore = minScore;
    }

    @Override
    public Query clone() {
        return new TopOneQuery(terms, weights, minScore);
    }

    @Override
    public double minScore() {
        return minScore;
    }

    @Override
    public void update(Item item, double score) {
        if (score < minScore) {
            throw new IllegalArgumentException(
                    "Query cannot be updated with item " + item + ". Given score is " +
                    score + " and minScore is " + minScore + ".");
        }
        minScore = score;
    }

    @Override
    public void setMinScore(double minScore) {
        this.minScore = minScore;
    }
}
// </editor-fold>